1 module lib.fs; 2 3 /** 4 Copy the given source path recursively to the target path 5 6 Params: 7 sourcePath = The source path which can be a file or a directory 8 targetPath = The target path which can be a file or a directory 9 */ 10 void copyRecurse(string sourcePath, string targetPath) 11 { 12 import std.file : isDir, exists, SpanMode, dirEntries, mkdir, mkdirRecurse, copy; 13 import std.path : buildPath, absolutePath, relativePath, buildNormalizedPath; 14 import std.parallelism; 15 16 if (!sourcePath.isDir) 17 { 18 return copy(sourcePath, targetPath); 19 } 20 21 const sourcePathAbsolute = sourcePath.absolutePath.buildNormalizedPath; 22 mkdirRecurse(targetPath); 23 foreach (directoryEntry; dirEntries(sourcePathAbsolute, SpanMode.breadth).parallel) 24 { 25 auto targetEntry = buildPath(targetPath, 26 directoryEntry.name.absolutePath.relativePath(sourcePathAbsolute)); 27 if (directoryEntry.isDir) 28 { 29 mkdir(targetEntry); 30 } 31 else 32 { 33 copy(directoryEntry.name, targetEntry); 34 } 35 } 36 } 37 38 import std.file : DirEntry, write, read, tempDir, exists, SpanMode, dirEntries; 39 import std.path : buildPath, extension, baseName; 40 import std.digest : hexDigest; 41 import std.digest.crc : CRC32; 42 43 /** Check if a folder has changed 44 Params: 45 rootDir = the directory to check 46 pattern = the pattern to check like "*.{d,di}" 47 Returns: 48 a boolean showing if the folder has changed 49 */ 50 bool folderHasChanged(string rootDir, string pattern) @trusted 51 { 52 const cached_name = buildPath(tempDir(), hexDigest!CRC32(pattern ~ rootDir.baseName())); 53 const newCache = getUniqueHash(rootDir, pattern); 54 if (!cached_name.exists()) 55 { 56 write(cached_name, newCache); 57 return true; 58 } 59 const changed = newCache != cast(ulong[]) read(cached_name); 60 if (changed) 61 { 62 write(cached_name, newCache); 63 } 64 return changed; 65 } 66 67 /** Get a unique cache for a directory 68 Params: 69 rootDir = the directory to check 70 pattern = the pattern to check like "*.{d,di}" 71 Returns: 72 a buffer of `ulong[]` 73 */ 74 ulong[] getUniqueHash(string rootDir, string pattern) @trusted 75 { 76 ulong[] buffer; 77 foreach (directoryEntry; dirEntries(rootDir, pattern, SpanMode.breadth)) 78 { 79 buffer ~= getUniqueHash(directoryEntry); 80 } 81 return buffer; 82 } 83 84 /// Get a unique hash for a DirEntry 85 /// https://github.com/WebFreak001/FSWatch/blob/1925700c64d9a26fbb2a6231b2cf94dc343800a4/source/fswatch.d#L38 86 ulong getUniqueHash(DirEntry entry) @trusted 87 { 88 version (Windows) 89 return entry.timeLastModified.stdTime ^ cast(ulong) entry.attributes; 90 else version (Posix) 91 return entry.statBuf.st_ino | (cast(ulong) entry.statBuf.st_dev << 32UL); 92 else 93 return (entry.timeLastModified.stdTime ^ ( 94 cast(ulong) entry.attributes << 32UL) ^ entry.linkAttributes) * entry.size; 95 } 96 97 unittest 98 { 99 copyRecurse("./dub.sdl", "./build/dub.sdl"); 100 assert("./build/dub.sdl".exists); 101 copyRecurse("./packages", "./build"); 102 assert("./build/packages".exists); 103 } 104 105 /** 106 Move the given source path recursively to the target path 107 108 Params: 109 sourcePath = The source path which can be a file or a directory 110 targetPath = The target path which can be a file or a directory 111 */ 112 void moveRecurse(string sourcePath, string targetPath) 113 { 114 import std.file : isDir, exists, SpanMode, dirEntries, mkdir, mkdirRecurse, 115 rename, rmdirRecurse; 116 import std.path : buildPath, absolutePath, relativePath, buildNormalizedPath; 117 import std.parallelism; 118 119 if (!sourcePath.isDir) 120 { 121 return rename(sourcePath, targetPath); 122 } 123 124 const sourcePathAbsolute = sourcePath.absolutePath.buildNormalizedPath; 125 mkdirRecurse(targetPath); 126 foreach (directoryEntry; dirEntries(sourcePathAbsolute, SpanMode.breadth).parallel) 127 { 128 auto targetEntry = buildPath(targetPath, 129 directoryEntry.name.absolutePath.relativePath(sourcePathAbsolute)); 130 if (directoryEntry.isDir) 131 { 132 mkdir(targetEntry); 133 } 134 else 135 { 136 rename(directoryEntry.name, targetEntry); 137 } 138 } 139 rmdirRecurse(sourcePath); 140 } 141 142 import std.file : rmdir, dirEntries, FileException, setAttributes, remove, attrIsDir, exists; 143 144 // TODO use dub to install rm-rf 145 // https://github.com/WebFreak001/rm-rf/blob/master/source/rm/rf.d 146 147 /** 148 Force remove the given directory recursively 149 150 Params: 151 pathname = The directory which should be deleted 152 */ 153 void rmdirRecurseForce(in char[] pathname) 154 { 155 if (!pathname.exists) 156 { 157 return; 158 } 159 //No references to pathname will be kept after rmdirRecurse, 160 //so the cast is safe 161 rmdirRecurseForce(DirEntry(cast(string) pathname)); 162 } 163 164 /// ditto 165 void rmdirRecurseForce(DirEntry de) 166 { 167 if (!de.isDir) 168 throw new FileException(de.name, "Not a directory"); 169 170 if (de.isSymlink) 171 { 172 version (Windows) 173 rmdir(de.name); 174 else 175 remove(de.name); 176 } 177 else 178 { 179 // all children, recursively depth-first 180 foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false)) 181 { 182 version (Windows) 183 { 184 import core.sys.windows.windows; 185 186 if ((e.attributes & FILE_ATTRIBUTE_READONLY) != 0) 187 setAttributes(e, e.attributes & ~FILE_ATTRIBUTE_READONLY); 188 } 189 attrIsDir(e.linkAttributes) ? rmdir(e.name) : remove(e.name); 190 } 191 192 // the dir itself 193 rmdir(de.name); 194 } 195 }